//
// UDevFS.cpp
// Usage: udev filesystem
// CreateDate: 2009-05-18
// Author: MaJiuyue
//
#include <SCI.h>
#include <RequestPackage.h>
#include "../schedule/Process.h"
#include "../schedule/Context.h"
#include "../ipc/MessageBox.h"
#include "UDevFS.h"

UDevFS::UDevFS()
	: FileSystem("UDevFS")
{
	// UDevFS
	root = new Directory("UDevFS", 0, 0, this);
}

UDevFS::~UDevFS()
{
	delete root;
}

int UDevFS::read(u32 inode, off_t off, size_t size, addr_t buf)
{
	u16 major = inode>>16;
	u16 minior = inode&0xFFFF;
	
	addr_t buffer_vaddr = buf&0xFFFFF000;
	size_t buffer_size = (size+PAGE_SIZE-1)&0xFFFFF000;

	pid_t driver_pid = m_DeviceMap.lookup(major);

	// Get device driver process
	Resource * res = ResourceManager.RequestResource(
		ServiceCallInterface::ProcessManager::ResourceName
	);
	Process * driver_proc = (Process *)res->CallSCI(
		ServiceCallInterface::ProcessManager::SCIID_GetProcessByPid,
		driver_pid
	);

	// Prepare request package
	size_t msg_size = sizeof(MESSAGE)+sizeof(RequestPackage);
	MESSAGE *msg = (MESSAGE *)malloc(msg_size);
	msg->size = msg_size;
	RequestPackage *IRP = (RequestPackage *)(&msg->dat);

	// Prepare buffer for driver(map buffer to driver's space)
	addr_t target = driver_proc->GetContext()->AllocUser(buffer_size);
	IRP->PrepareReadIRP(GetCurrentProcess()->GetPID(), minior, off, target+(buf&0xFFF), size);

	Context * context = GetCurrentProcess()->GetContext();
	for( size_t nbytes=0; nbytes<buffer_size; nbytes+=PAGE_SIZE)
	{
		// Get physcial address of buffer
		addr_t paddr = context->GetPhyAddress(buffer_vaddr);
		driver_proc->GetContext()->MapPage(target, paddr);

		buffer_vaddr += PAGE_SIZE;
		target += PAGE_SIZE;
	}

	// Send request
	res = ResourceManager.RequestResource(
		ServiceCallInterface::IPCCenter::ResourceName
	);
	res->CallSCI(
		ServiceCallInterface::IPCCenter::SCIID_SendKNL,
		driver_pid,
		(u32)msg
	);

	return EJUSTRETURN;
}

int UDevFS::write(u32 inode, off_t off, size_t size, addr_t buf)
{
	u16 major = inode>>16;
	u16 minior = inode&0xFFFF;
	
	addr_t buffer_vaddr = buf&0xFFFFF000;
	size_t buffer_size = (size+PAGE_SIZE-1)&0xFFFFF000;

	pid_t driver_pid = m_DeviceMap.lookup(major);

	// Get device driver process
	Resource * res = ResourceManager.RequestResource(
		ServiceCallInterface::ProcessManager::ResourceName
	);
	Process * driver_proc = (Process *)res->CallSCI(
		ServiceCallInterface::ProcessManager::SCIID_GetProcessByPid,
		driver_pid
	);

	// Prepare request package
	size_t msg_size = sizeof(MESSAGE)+sizeof(RequestPackage);
	MESSAGE *msg = (MESSAGE *)malloc(msg_size);
	msg->size = msg_size;
	RequestPackage *IRP = (RequestPackage *)(&msg->dat);

	// Prepare buffer for driver(map buffer to driver's space)
	addr_t target = driver_proc->GetContext()->AllocUser(buffer_size);
	IRP->PrepareWriteIRP(GetCurrentProcess()->GetPID(), minior, off, target+(buf&0xFFF), size);

	Context * context = GetCurrentProcess()->GetContext();
	for( size_t nbytes=0; nbytes<buffer_size; nbytes+=PAGE_SIZE)
	{
		// Get physcial address of buffer
		addr_t paddr = context->GetPhyAddress(buffer_vaddr);
		driver_proc->GetContext()->MapPage(target, paddr);

		buffer_vaddr += PAGE_SIZE;
		target += PAGE_SIZE;
	}

	// Send request
	res = ResourceManager.RequestResource(
		ServiceCallInterface::IPCCenter::ResourceName
	);
	res->CallSCI(
		ServiceCallInterface::IPCCenter::SCIID_SendKNL,
		driver_pid,
		(u32)msg
	);

	return EJUSTRETURN;
}

File * UDevFS::CreateDevice(const char *name, pid_t pid, u16 major, u16 cnt)
{
	File * dev = NULL;
	char * dev_name = NULL;
	u32 inode = 0;
	int name_len = strlen(name);

	// Max minior device is 8
	if(cnt>8)
		panic("UDevFS can handle only 8 minior device!");

	// Check if 'major' device already mapped
	if(m_DeviceMap.lookup(major)!=0)
		panic("Device file with major \"%d\" already exist!", major);

	/* Create device file */
	dev_name = new char[name_len+2];
	strcpy(dev_name, name);
	dev_name[name_len] = '0';
	dev_name[name_len+1] = '\0';

	for(u16 i=0; i<cnt; i++)
	{
		// Generate inode for device
		inode = major<<16 | i;
		// Create device file
		dev = new DeviceFile(dev_name, inode, 0, this);
		// Add to devRoot
		if(root->AddEntry(dev)!=0)
			panic("Add device file '%s' failed, same name exist.", dev_name);

		dev_name[name_len]++;
	}

	/* Add device_inode<-->driver_pid map */
	m_DeviceMap.add(major, pid);
}

File * UDevFS::TraversalPath(Directory *target, const char *path)
{
	return NULL;
}

